#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
视频正倒放拼接处理器

该模块提供了一个视频处理功能，可以将输入视频进行正放和倒放交替拼接，
并根据指定的目标时长截取最终视频。

注意：此模块适配MoviePy 2.x版本，使用subclipped方法替代了旧版本的subclip方法。
"""

import logging
import os
import subprocess
import traceback
import hashlib
from moviepy.video.io.VideoFileClip import VideoFileClip
from moviepy import concatenate_videoclips
from moviepy.video.fx import TimeMirror
from utils import get_video_info
from utils.download_utils import is_url, download_video, cleanup_downloaded_file
from utils.oss_utils import get_oss_uploader

# 配置日志
logger = logging.getLogger(__name__)


def reverse_splice_processor(video_path, target_duration=None, task_id=None, oss_bucket_dir="freefishpc/cloud_video/dispose", **kwargs):
    """
    根据目标时长将视频进行正倒放拼接
    
    Args:
        video_path: 输入视频文件路径（支持本地路径和网络URL）
        target_duration: 目标时长（秒），如果为None则使用原视频时长的2倍
        task_id: 任务ID，用于生成唯一的输出文件名
        oss_bucket_dir: OSS存储桶中的目录路径
        **kwargs: 其他可选参数
    
    Returns:
        tuple: (处理后的视频路径或OSS链接, 视频信息字典)
    """
    logger.info(f"开始视频正倒放拼接处理 - 文件: {video_path}, 目标时长: {target_duration}")
    
    # 使用项目目录下的download文件夹
    project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    download_dir = os.path.join(project_dir, "download")
    downloaded_file = None
    
    try:
        # 如果是网络URL，先下载视频
        if is_url(video_path):
            logger.info(f"检测到网络链接，开始下载视频: {video_path}")
            try:
                local_path = download_video(video_path, download_dir)
                downloaded_file = local_path
                video_path = local_path
                logger.info(f"视频下载完成: {video_path}")
            except Exception as e:
                logger.error(f"下载视频失败: {video_path} - {e}")
                raise Exception(f"下载视频失败: {video_path} - {e}")
        
        # 1. 验证文件是否存在且大小合理
        if not os.path.exists(video_path):
            raise FileNotFoundError(f"视频文件不存在: {video_path}")
        
        file_size = os.path.getsize(video_path)
        if file_size < 1024:  # 文件大小小于1KB，可能是无效文件
            raise ValueError(f"视频文件太小，可能是无效文件: {video_path}, 大小: {file_size} 字节")
        
        # 2. 尝试使用ffprobe预先检查视频文件格式
        try:
            # 检查文件是否为有效的视频文件
            result = subprocess.run(
                ['ffprobe', '-v', 'error', '-show_entries', 'stream=codec_type', '-of', 'default=noprint_wrappers=1:nokey=1', video_path],
                capture_output=True, text=True, timeout=10
            )
            
            if result.returncode == 0:
                output = result.stdout.lower()
                if 'video' not in output:
                    raise ValueError(f"文件不包含视频流: {video_path}")
                logger.info(f"ffprobe验证通过，文件包含视频流: {video_path}")
            else:
                logger.warning(f"ffprobe验证失败，但将继续尝试处理视频: {video_path}, 错误: {result.stderr}")
        except (subprocess.SubprocessError, FileNotFoundError):
            # 如果ffprobe不可用，跳过这个验证
            logger.warning("ffprobe不可用，无法预先验证视频格式")
        
        # 3. 使用多种方法尝试打开视频，增加兼容性
        video = None
        # 方法1: 标准打开方式
        try:
            video = VideoFileClip(video_path)
            # 尝试读取第一帧以验证视频的有效性
            first_frame = video.get_frame(0)
            logger.info(f"成功以标准方式打开视频并读取第一帧: {video_path}")
        except Exception as frame_error:
            logger.error(f"标准方式无法读取视频文件的第一帧: {video_path}, 错误: {str(frame_error)}")
            logger.debug(f"标准方式打开视频错误详情:\n{traceback.format_exc()}")
            video = None
        original_duration = video.duration
        
        # 如果未指定目标时长，使用原视频时长的2倍
        if target_duration is None:
            target_duration = original_duration * 2
            logger.info(f"未指定目标时长，使用原视频时长的2倍: {target_duration}秒")
        
        # 确保目标时长不小于原视频时长
        if target_duration < original_duration:
            logger.warning(f"目标时长小于原视频时长，将使用原视频时长: {original_duration}秒")
            target_duration = original_duration
        
        # 计算需要拼接的片段数量
        # 基础模式：正放 + 倒放
        # 如果目标时长超过这个长度，则需要重复拼接
        base_duration = original_duration * 2
        repeat_count = 1
        
        if target_duration > base_duration:
            repeat_count = int(target_duration / base_duration) + 1
            logger.info(f"目标时长超过基础时长，需要重复拼接: {repeat_count}次")
        
        # 创建视频片段列表
        video_clips = []
        
        # 生成需要的视频片段
        for i in range(repeat_count):
            # 添加正放片段
            video_clips.append(video)
            # 添加倒放片段
            # 修复TimeMirror深层嵌套递归错误：使用更安全的处理方式
            # 对于同时包含视频和音频的剪辑，我们需要分别处理以避免递归调用
            try:
                # 创建视频副本以避免修改原始剪辑
                video_copy = video.subclipped(0, video.duration)
                
                # 音频处理修复：如果视频包含音频，需要对音频也应用TimeMirror效果的修复
                if video_copy.audio is not None:
                    try:
                        # 分离视频和音频处理以避免递归错误
                        # 先处理视频部分（不含音频）
                        video_without_audio = video_copy.without_audio()
                        reversed_video = video_without_audio.with_effects([TimeMirror()])
                        
                        # 再处理音频部分（如果需要）
                        try:
                            # 创建音频副本
                            audio_copy = video_copy.audio.subclipped(0, video_copy.audio.duration)
                            # 对音频应用TimeMirror效果（使用更安全的方法）
                            reversed_audio = audio_copy.with_effects([TimeMirror()])
                            # 合并处理后的视频和音频
                            reversed_clip = reversed_video.with_audio(reversed_audio)
                        except Exception as audio_processing_error:
                            logger.warning(f"音频TimeMirror处理失败，将移除音频轨道: {str(audio_processing_error)}")
                            # 如果音频处理失败，只使用处理后的视频
                            reversed_clip = reversed_video
                    except Exception as audio_separation_error:
                        logger.warning(f"音频分离处理失败，将移除音频轨道: {str(audio_separation_error)}")
                        # 如果音频分离失败，移除音频轨道以避免深层嵌套错误
                        video_copy = video_copy.without_audio()
                        reversed_clip = video_copy.with_effects([TimeMirror()])
                else:
                    # 无音频视频直接处理
                    reversed_clip = video_copy.with_effects([TimeMirror()])
            except Exception as clip_error:
                logger.error(f"创建倒放片段时发生错误: {str(clip_error)}")
                # 如果创建倒放片段失败，使用原始视频作为后备方案
                reversed_clip = video.with_effects([TimeMirror()])
            video_clips.append(reversed_clip)
        
        # 计算总时长
        total_duration = sum(clip.duration for clip in video_clips)
        
        # 拼接所有片段
        final_clip = concatenate_videoclips(video_clips)
        
        # 如果总时长超过目标时长，截取前target_duration秒
        if total_duration > target_duration:
            # 截取到目标时长
            # 注意：MoviePy 2.x使用subclipped方法替代了旧版本的subclip方法
            final_clip = final_clip.subclipped(0, target_duration)
            logger.info(f"拼接后总时长超过目标时长，已截取到目标时长: {target_duration}秒")
        else:
            logger.info(f"视频片段拼接完成，总时长: {final_clip.duration}秒")
        
        # 生成输出文件路径
        base_name = os.path.basename(video_path)
        name_without_ext = os.path.splitext(base_name)[0]
        
        # 使用项目目录下的output文件夹
        project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        output_dir = os.path.join(project_dir, "output")
        # 确保输出目录存在
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
        
        # 生成带task_id的输出文件名
        if task_id:
            output_filename = f"{name_without_ext}_reverse_spliced_{task_id}.mp4"
        else:
            # 如果没有提供task_id，使用时间戳
            import datetime
            current_time = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            output_filename = f"{name_without_ext}_reverse_spliced_{current_time}.mp4"
        
        output_path = os.path.join(output_dir, output_filename)
        
        # 导出处理后的视频
        logger.info(f"开始导出处理后的视频 - 输出路径: {output_path}")
        final_clip.write_videofile(
            output_path,
            codec="libx264",
            audio_codec="aac",
            threads=kwargs.get('threads', 4),
            fps=video.fps,
            preset="medium"
        )
        
        logger.info(f"视频处理完成 - 输出路径: {output_path}")
        
        # 获取处理后的视频信息
        video_info = get_video_info(output_path)
        
        # 关闭所有视频片段
        if video is not None:
            try:
                video.close()
            except:
                pass  # 忽略关闭时的错误
        if final_clip is not None:
            try:
                final_clip.close()
            except:
                pass  # 忽略关闭时的错误
        if 'video_clips' in locals():
            for clip in video_clips:
                # 确保clip不是None且不与video或final_clip相同
                if clip is not None:
                    # 避免重复关闭相同的对象
                    if clip is not video and clip is not final_clip:
                        try:
                            clip.close()
                        except:
                            pass  # 忽略关闭时的错误
        
        # 上传到OSS
        final_output_path = output_path
        try:
            logger.info("开始上传视频到OSS")
            # 创建OSS上传器（自动使用项目配置的环境）
            oss_uploader = get_oss_uploader()
            
            if oss_uploader:
                # 使用task_id作为OSS对象键的一部分，如果没有task_id则使用时间戳
                if task_id:
                    oss_object_key = f"{oss_bucket_dir}/{task_id}.mp4"
                else:
                    # 如果没有提供task_id，使用时间戳
                    import datetime
                    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
                    oss_object_key = f"{oss_bucket_dir}/reverse_splice_{timestamp}.mp4"
                
                # 上传文件到OSS
                oss_url = oss_uploader.upload_file(output_path, oss_object_key)
                logger.info(f"视频已上传到OSS: {oss_url}")
                
                # 更新返回的路径为OSS链接
                final_output_path = oss_url
            else:
                logger.warning("无法创建OSS上传器，返回本地文件路径")
        except Exception as e:
            logger.error(f"上传视频到OSS失败: {e}")
            # 如果上传失败，仍然返回本地文件路径
        
        # 清理下载的文件
        cleanup_downloaded_file(downloaded_file)
        
        return final_output_path, video_info
    
    except Exception as e:
        # 清理下载的文件
        cleanup_downloaded_file(downloaded_file)
        
        # 关闭所有视频片段
        if 'video' in locals() and video is not None:
            try:
                video.close()
            except:
                pass  # 忽略关闭时的错误
        if 'final_clip' in locals() and final_clip is not None:
            try:
                final_clip.close()
            except:
                pass  # 忽略关闭时的错误
        if 'video_clips' in locals():
            for clip in video_clips:
                # 确保clip不是None
                if clip is not None:
                    # 避免重复关闭相同的对象
                    if clip is not video and clip is not final_clip:
                        try:
                            clip.close()
                        except:
                            pass  # 忽略关闭时的错误
        
        # 记录详细的错误日志
        logger.error(f"视频正倒放拼接处理失败: {str(e)}")
        logger.debug(f"错误详情:\n{traceback.format_exc()}")
        
        # 重新抛出异常
        raise